package org.artifact.core.context.bytebuf;

import java.util.Collection;
import java.util.Date;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.artifact.core.constant.ArtifactErrorCode;
import org.artifact.core.exception.ArtifactExceptionUtil;
import org.artifact.core.lang.ISerializable;

import cn.hutool.core.util.ClassUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;

@SuppressWarnings({ "rawtypes", "unchecked" })
public abstract class IByteBuff {

	public void writeBytes(byte[] value) {
		onWriteBytes(value);
	}

	public void writeByte(byte value) {
		onWriteByte(ProxyContent.BYTE);
		onWriteByte(value);
	}

	public void writeShort(short value) {
		if (value >= ProxyContent.BYTE_MIN_VALUE && value <= ProxyContent.BYTE_MAX_VALUE) {
			onWriteByte(ProxyContent.SHORT_8);
			onWriteByte((byte) value);
		} else {
			onWriteByte(ProxyContent.SHORT_16);
			onWriteShort(value);
		}
	}

	public void writeInt(int value) {
		Byte _byte = ProxyContent.INT_SUB_TYPE.getByKey(value);
		if (_byte != null) {
			onWriteByte(_byte);
		} else if (value >= ProxyContent.BYTE_MIN_VALUE && value <= ProxyContent.BYTE_MAX_VALUE) {
			onWriteByte(ProxyContent.INT_8);
			onWriteByte((byte) value);
		} else if (value >= ProxyContent.SHORT_MIN_VALUE && value <= ProxyContent.SHORT_MAX_VALUE) {
			onWriteByte(ProxyContent.INT_16);
			onWriteShort((short) value);
		} else {
			onWriteByte(ProxyContent.INT_32);
			onWriteInt(value);
		}
	}

	public void writeLong(long value) {
		if (value >= ProxyContent.BYTE_MIN_VALUE && value <= ProxyContent.BYTE_MAX_VALUE) {
			onWriteByte(ProxyContent.LONG_8);
			onWriteByte((byte) value);
		} else if (value >= ProxyContent.SHORT_MIN_VALUE && value <= ProxyContent.SHORT_MAX_VALUE) {
			onWriteByte(ProxyContent.LONG_16);
			onWriteShort((short) value);
		} else if (value >= ProxyContent.INT_MIN_VALUE && value <= ProxyContent.INT_MAX_VALUE) {
			onWriteByte(ProxyContent.LONG_32);
			onWriteInt((int) value);
		} else {
			onWriteByte(ProxyContent.LONG_64);
			onWriteLong(value);
		}
	}

	public void writeFloat(float value) {
		onWriteByte(ProxyContent.FLOAT);
		onWriteFloat(value);
	}

	public void writeDouble(double value) {
		if (value >= ProxyContent.FLOAT_MIN_VALUE && value <= ProxyContent.FLOAT_MAX_VALUE) {
			onWriteByte(ProxyContent.DOUBLE_32);
			onWriteFloat((float) value);
		} else {
			onWriteByte(ProxyContent.DOUBLE_64);
			onWriteDouble(value);
		}
	}

	public void writeBoolean(boolean value) {
		onWriteByte(value ? ProxyContent.TRUE : ProxyContent.FALSE);
	}

	public void writeNull() {
		onWriteByte(ProxyContent.NULL);
	}

	public void writeObject(Object value) {
		if (value == null) {
			writeNull();
		} else if (value instanceof Byte) {
			writeByte((byte) value);
		} else if (value instanceof Short) {
			writeShort((short) value);
		} else if (value instanceof Integer) {
			writeInt((int) value);
		} else if (value instanceof Long) {
			writeLong((long) value);
		} else if (value instanceof Float) {
			writeFloat((float) value);
		} else if (value instanceof Double) {
			writeDouble((double) value);
		} else if (value instanceof Boolean) {
			writeBoolean((boolean) value);
		} else if (value instanceof String) {
			writeString((String) value);
		} else if (value instanceof Date) {
			writeDate((Date) value);
		} else if (value instanceof Map) {
			writeMap((Map) value);
		} else if (value instanceof Collection) {
			writeCollection((Collection<Object>) value);
		} else if (value instanceof ISerializable) {
			writeSerializable((ISerializable) value);
		} else if (value instanceof Enum) {
			writeEnum((Enum<?>) value);
		}
	}

	public void writeString(String value) {
		if (value == null) {
			writeNull();
			return;
		}
		byte[] data = StrUtil.bytes(value, ProxyContent.CHARSET);
		Byte _byte = ProxyContent.STRING_SUB_TYPE.getByKey(data.length);
		if (_byte != null) {
			onWriteByte(_byte);
			onWriteBytes(data);
		} else {
			onWriteByte(ProxyContent.STRING);
			writeInt(data.length);
			onWriteBytes(data);
		}
	}

	public void writeDate(Date value) {
		if (value == null) {
			writeNull();
			return;
		}
		onWriteByte(ProxyContent.DATE);
		writeLong(value.getTime());
	}

	public void writeMap(Map value) {
		if (value == null) {
			writeNull();
			return;
		}
		Byte _byte = ProxyContent.MAP_SUB_TYPE.getByKey(value.size());
		if (_byte != null) {
			onWriteByte(_byte);
		} else {
			onWriteByte(ProxyContent.MAP);
			writeInt(value.size());
		}
		writeString(value.getClass().getName());
		Set<Entry> entrys = value.entrySet();
		for (Map.Entry entry : entrys) {
			writeObject(entry.getKey());
			writeObject(entry.getValue());
		}
	}

	public void writeCollection(Collection value) {
		if (value == null) {
			writeNull();
			return;
		}
		Byte _byte = ProxyContent.COLLECTION_SUB_TYPE.getByKey(value.size());
		if (_byte != null) {
			onWriteByte(_byte);
		} else {
			onWriteByte(ProxyContent.COLLECTION);
			writeInt(value.size());
		}
		writeString(value.getClass().getName());
		for (Object object : value) {
			writeObject(object);
		}
	}

	public void writeSerializable(ISerializable value) {
		if (value == null) {
			writeNull();
			return;
		}
		onWriteByte(ProxyContent.ISERIALIZABLE);
		writeString(value.getClass().getName());
		value.toByteBuff(this);
	}

	public void writeEnum(Enum value) {
		if (value == null) {
			writeNull();
			return;
		}
		onWriteByte(ProxyContent.ENUM);
		writeString(value.getClass().getName());
		writeInt(value.ordinal());
	}

	public byte[] readBytes(int length) {
		return onReadBytes(length);
	}

	public byte readByte() {
		ArtifactExceptionUtil.isTrue(ArtifactErrorCode.UNKNOWN_ERROR, onReadByte() != ProxyContent.BYTE);
		return onReadByte();
	}

	public short readShort() {
		byte tag = onReadByte();
		switch (tag){
			case ProxyContent.SHORT_8:
				return onReadByte();
			case ProxyContent.SHORT_16:
				return onReadShort();
		}
		throw ArtifactExceptionUtil.error(ArtifactErrorCode.UNKNOWN_ERROR);
	}

	public int readInt() {
		byte tag = onReadByte();
		Integer _byte = ProxyContent.INT_SUB_TYPE.getbyValue(tag);
		if(_byte != null){
			return _byte;
		}else{
			switch (tag){
				case ProxyContent.INT_8:
					return onReadByte();
				case ProxyContent.INT_16:
					return onReadShort();
				case ProxyContent.INT_32:
					return onReadInt();
			}
			throw ArtifactExceptionUtil.error(ArtifactErrorCode.UNKNOWN_ERROR);
		}
	}

	public long readLong() {
		byte tag = onReadByte();
		switch (tag){
			case ProxyContent.LONG_8:
				return onReadByte();
			case ProxyContent.LONG_16:
				return onReadShort();
			case ProxyContent.LONG_32:
				return onReadInt();
			case ProxyContent.LONG_64:
				return onReadLong();
		}
		throw ArtifactExceptionUtil.error(ArtifactErrorCode.UNKNOWN_ERROR);
	}

	public float readFloat() {
		ArtifactExceptionUtil.isTrue(ArtifactErrorCode.UNKNOWN_ERROR, onReadByte() != ProxyContent.FLOAT);
		return onReadFloat();
	}

	public double readDouble() {
		byte tag = onReadByte();
		switch (tag){
			case ProxyContent.DOUBLE_32:
				return onReadFloat();
			case ProxyContent.DOUBLE_64:
				return onReadDouble();
		}
		throw ArtifactExceptionUtil.error(ArtifactErrorCode.UNKNOWN_ERROR);
	}

	public boolean readBoolean() {
		return onReadByte() == ProxyContent.TRUE;
	}

	public <T> T readNull() {
		ArtifactExceptionUtil.isTrue(ArtifactErrorCode.UNKNOWN_ERROR, onReadByte() != ProxyContent.NULL);
		return null;
	}

	public Object readObject() {
		byte tag = getTag();
		if (tag == ProxyContent.NULL) {
			return readNull();
		} else if (tag == ProxyContent.BYTE) {
			return readByte();
		} else if (tag == ProxyContent.SHORT_8 || tag == ProxyContent.SHORT_16) {
			return readShort();
		} else if (ProxyContent.INT_SUB_TYPE.containsValue(tag) || tag == ProxyContent.INT_8 || tag == ProxyContent.INT_16 || tag == ProxyContent.INT_32) {
			return readInt();
		} else if (tag == ProxyContent.LONG_8 || tag == ProxyContent.LONG_16 || tag == ProxyContent.LONG_32 || tag == ProxyContent.LONG_64) {
			return readLong();
		} else if (tag == ProxyContent.FLOAT) {
			return readFloat();
		} else if (tag == ProxyContent.DOUBLE_32 || tag == ProxyContent.DOUBLE_64) {
			return readDouble();
		} else if (tag == ProxyContent.STRING || ProxyContent.STRING_SUB_TYPE.containsValue(tag)) {
			return readString();
		} else if (tag == ProxyContent.DATE) {
			return readDate();
		} else if (tag == ProxyContent.MAP || ProxyContent.MAP_SUB_TYPE.containsValue(tag)) {
			return readMap();
		} else if (tag == ProxyContent.COLLECTION || ProxyContent.COLLECTION_SUB_TYPE.containsValue(tag)) {
			return readCollection();
		} else if (tag == ProxyContent.TRUE || tag == ProxyContent.FALSE) {
			return readBoolean();
		} else if (tag == ProxyContent.ISERIALIZABLE) {
			return readSerializable();
		} else if (tag == ProxyContent.ENUM) {
			return readEnum();
		} else {
			throw ArtifactExceptionUtil.error(ArtifactErrorCode.UNKNOWN_ERROR);
		}
	}

	public String readString() {
		byte tag = getTag();
		switch (tag) {
		case ProxyContent.NULL:
			return readNull();
		case ProxyContent.STRING:
			skipBytes(Byte.BYTES);
			return StrUtil.str(onReadBytes(readInt()), ProxyContent.CHARSET);
		default:
			Integer _int = ProxyContent.STRING_SUB_TYPE.getbyValue(tag);
			ArtifactExceptionUtil.isTrue(ArtifactErrorCode.UNKNOWN_ERROR, _int == null);
			skipBytes(Byte.BYTES);
			return StrUtil.str(onReadBytes(_int), ProxyContent.CHARSET);
		}
	}

	public Date readDate() {
		byte tag = getTag();
		switch (tag) {
		case ProxyContent.NULL:
			return readNull();
		case ProxyContent.DATE:
			skipBytes(Byte.BYTES);
			return new Date(readLong());
		default:
			throw ArtifactExceptionUtil.error(ArtifactErrorCode.UNKNOWN_ERROR);
		}
	}

	public Map readMap() {
		byte tag = getTag();

		int size = 0;
		switch (tag) {
		case ProxyContent.NULL:
			return readNull();
		case ProxyContent.MAP:
			skipBytes(Byte.BYTES);
			size = readInt();
		default:
			Integer _int = ProxyContent.MAP_SUB_TYPE.getbyValue(tag);
			ArtifactExceptionUtil.isTrue(ArtifactErrorCode.UNKNOWN_ERROR, _int == null);
			skipBytes(Byte.BYTES);
			size = _int;
		}
		String className = readString();
		Map map = ReflectUtil.newInstance(className);
		for (int i = 0; i < size; i++) {
			map.put(readObject(), readObject());
		}
		return map;
	}

	public Collection<?> readCollection() {
		byte tag = getTag();
		int size = 0;
		switch (tag) {
		case ProxyContent.NULL:
			return readNull();
		case ProxyContent.COLLECTION:
			skipBytes(Byte.BYTES);
			size = readInt();
		default:
			Integer _int = ProxyContent.COLLECTION_SUB_TYPE.getbyValue(tag);
			ArtifactExceptionUtil.isTrue(ArtifactErrorCode.UNKNOWN_ERROR, _int == null);
			skipBytes(Byte.BYTES);
			size = _int;
		}
		String className = readString();
		Collection list = ReflectUtil.newInstance(className);
		for (int i = 0; i < size; i++) {
			list.add(readObject());
		}
		return list;
	}

	public ISerializable<?> readSerializable() {
		byte tag = getTag();
		switch (tag) {
		case ProxyContent.NULL:
			return readNull();
		case ProxyContent.ISERIALIZABLE:
			skipBytes(Byte.BYTES);
			String className = readString();
			ISerializable<?> serializable = ReflectUtil.newInstance(className);
			serializable.forByteBuff(this);
			return serializable;
		default:
			throw ArtifactExceptionUtil.error(ArtifactErrorCode.UNKNOWN_ERROR);
		}
	}

	public Enum<?> readEnum() {
		byte tag = getTag();
		switch (tag) {
		case ProxyContent.NULL:
			return readNull();
		case ProxyContent.ENUM:
			skipBytes(Byte.BYTES);
			String className = readString();
			int ordinal = readInt();
			Enum<?> obj = (Enum<?>) ClassUtil.loadClass(className).getEnumConstants()[ordinal];
			return obj;
		default:
			throw ArtifactExceptionUtil.error(ArtifactErrorCode.UNKNOWN_ERROR);
		}
	}

	public abstract void onWriteBytes(byte[] value);

	public abstract void onWriteByte(byte value);

	public abstract void onWriteShort(short value);

	public abstract void onWriteInt(int value);

	public abstract void onWriteLong(long value);

	public abstract void onWriteFloat(float value);

	public abstract void onWriteDouble(double value);

	public abstract void onWriteBoolean(boolean value);

	public abstract byte[] onReadBytes(int length);

	public abstract byte onReadByte();

	public abstract short onReadShort();

	public abstract int onReadInt();

	public abstract long onReadLong();

	public abstract float onReadFloat();

	public abstract double onReadDouble();

	public abstract boolean onReadBoolean();

	public abstract byte getTag();

	public abstract void skipBytes(int length);

	public abstract void clear();

	public abstract int getReaderIndex();

	public abstract void setReaderIndex(int readerIndex);

	public abstract int getWriterIndex();

	public abstract void setWriterIndex(int writerIndex);

	public abstract int readableBytes();

	public abstract byte[] array();
}
